<?php
session_start(); // 开启session功能
/*
 * IntegrationTestingLite
 * 开源作品-PHP在线集成测试工具(单文件绿色版)
 * 程序特性：
 *      1. 单文件绿色版,部署超简单
 *      2. 支持多个数据库服务器切换
 * 使用方法：
 *      1. 复制本文件到web任意目录
 *      2. 修改$testconfig配置信息为实际内容
 *      3. 在浏览器输入链接路径,开始使用
 * 说明：写这个程序主要是为了辅助项目开发中的流程测试.如果您有兴趣一起完善这个小程序,欢迎使用git一起开发~感谢！
 * 项目周期：
 *      2017-10-23 立项开发
 *      2017-10-24 程序完成
 * 版权：不限商业用途! 如有修改代码请保留原著署名,并把修改后的代码回馈到作者邮箱：<14507247@qq.com>,促进本产品进步.做一个有公德心的正能量程序员~
 *      作者博客：http://www.cnblogs.com/sochishun/ 欢迎交流~
 * @since 1.0 <2017-10-23> SoChishun <14507247@qq.com> Added.
 * @since 1.1 <2017-10-27> SoChishun 新增倒计时自动刷新功能.
 * @since 1.2 <2017-11-2> SoChishun 改进计时器功能; 新增导出到Markdown文件功能.
 * @since 1.3 <2017-11-6 > SoChishun 新增host配置,并支持多个host下拉切换
 * @since 1.4 <2017-12-19> SoChishun 登录方法封装成JWTLite类,方便管理和迁移
 */
// 版本号
define('VERSION', '1.4.0');
// 程序名称
define('APPNAME', 'IntegrationTestingLite');

// TOKEN_EXP 超时时间 (1 hours, 1 minutes, 30 seconds)
JWTLite::$TOKEN_EXP = '1 hours';
// TOKEN_AUD 允许运行的服务器列表(适用于分布式环境,如果是负载均衡+高防IP环境,则填入高防IP即可),多个之间以逗号隔开,中间不要有空格
JWTLite::$TOKEN_AUD = '127.0.0.1,51.66.99.100';
// TOKEN签名密钥
JWTLite::$TOKEN_KEY = 'U6rxInZ7!';
// TOKEN自动延期时长, 每次页面跳转操作会重新生成一个延期过的TOKEN,类似session的效果,只要一直操作就一直不会过期
JWTLite::$TOKEN_REFRESH_SECOND = 1800;

/**
 * 页面访问验证配置
 */
JWTLite::$AUTH_CONFIG = [
    'admins' => ['admin' => 'passWd123', 'testuser' => 'passWd123'], // 允许登录的帐号列表['登录名'=>'密码']
    'ips' => ['127.0.0.1', '110.52.27.168'], // 允许访问的ip地址列表
    'current_authtype' => 1, // 指定页面访问验证类型：0:无需验证;1:帐号密码验证;2:IP验证;3:混合验证(先IP验证,IP验证失败则转入帐号密码验证)
];

// 页面访问验证配置
JWTLite::check_login();
list($loginid, $token, $loginexp) = array_values(JWTLite::$LOGIN_RESULT);

/**
 * 测试项目配置
 */
$testingConfig = array(
    /* 服务器-remote */
    'remote' => array(
        'host' => ['127.0.0.1', 'www.test1.com', 'www.test2.cn'], // 服务器,支持多个替换
        /* 项目 */
        'project' => array(
            'test_cps_api' => array(
                'title' => '测试CPS接口',
                'items' => array(
                    '1.1. 主播ID绑定业务主播接口（B端-业务员管理功能使用）' => '~/mapi/ylapi.php?ctl=ylcpsapi&act=checkAnchorExistsForYLB',
                    '1.2. 会员查询接口（B端-会员查询功能）' => '~/mapi/ylapi.php?ctl=ylcpsapi&act=getMembersForYLB',
                    '1.4. A端-会员归属推广ID修改（A端-会员关系修改功能）' => '~/mapi/ylapi.php?ctl=ylcpsapi&act=changeCUserForYLB',
                    '1.5. 会员列表接口（A端-会员列表功能）' => '~/mapi/ylapi.php?ctl=ylcpsapi&act=getMembersForYLA',
                    '1.6. cps用户关系同步接口' => '~/mapi/ylapi.php?ctl=ylcpsapi&act=snycMemberRelationshipForYLCPS',
                    '1.7. 获取推广员/主播接口' => '~/mapi/ylapi.php?ctl=ylcpsapi&act=getAnchorOrCuserForYL'
                )
            ),
            'test_guessing' => array(
                'title' => '测试网综竞猜',
                'items' => array(
                    '观众登录' => '~/mapi/index.php?ctl=login&act=do_login&mobile=13950076987&verify_coder=9999',
                    '进入直播房间' => '~/mapi/index.php?ctl=video&act=get_video2&sdk_type=android&room_id=1926',
                    '观众下注' => '~/mapi/index.php?ctl=guessing&act=bet&guessing_log_id=3&guessing_id=13&money=100',
                    '竞猜开奖' => '~/mapi/index.php?ctl=guessing&act=draw&xiacai_id=7&video_id=1926&guessing_id=0',
                    '竞猜开始' => '~/mapi/index.php?ctl=guessing&act=start&xiacai_id=7&video_id=1926',
                    '开奖弹窗信息' => '~/mapi/index.php?ctl=guessing&act=userDiamonds&guessing_log_id=8&video_id=1928',
                    '查看历史开奖记录' => '~/mapi/index.php?ctl=guessing&act=log&video_id=1928',
                    '友盟推送' => '~/mapi/index.php?ctl=guessing&act=ymts&user_id=100515&video_id=1928',
                ),
                'replace_vars' => array('room_id' => 1926, 'video_id' => 1926, 'guessing_log_id' => 8, 'xiacai_id' => 7, 'guessing_id' => 14),
            ),
            'test_missing' => array(
                'title' => '测试每日任务',
                'items' => array(
                    '用户登录' => '~/mapi/index.php?ctl=login&act=do_login&mobile=13950076987&verify_coder=9999',
                    '获取任务列表' => '~/mapi/index.php?ctl=mission&act=getMissionList',
                    '打赏主播10次' => '~/mapi/index.php?ctl=deal&act=pop_prop&prop_id=68&num=1&room_id=2069',
                    '领取任务奖励' => '~/mapi/index.php?ctl=mission&act=commitMission&type=1',
                ),
            ),
            'test_obs' => array(
                'title' => '测试OBS直播',
                'items' => array(
                    '主播登录' => '~/mapi/index.php?ctl=login&act=do_login&mobile=13950076987&verify_coder=9999',
                    '创建房间状态回调' => '~/mapi/index.php?ctl=video&act=video_cstatus&room_id=2069&status=1&channelid=0&play_rtmp=&play_flv=&play_hls=&group_id=',
                    '主播加入直播' => '~/mapi/index.php?ctl=video&act=add_video&sdk_version_name=2.4.1&i_type=1&screen_width=1080&screen_height=1920&sdk_type=android&sdk_version=2017092010&xpoint=118.196693&ypoint=24.487791999999999&title=直播&cate_id=32&city=厦门市&province=福建省&location_switch=1&is_private=0',
                    '观看直播' => '~/mapi/index.php?ctl=video&act=get_video2&sdk_type=android&room_id=1926',
                    '结束直播' => '~/mapi/index.php?ctl=video&act=end_video&room_id=2069&video_url=',
                ),
            ),
            'test_login' => array(
                'title' => '测试手机号密码登录',
                'items' => array(
                    '手机号+验证码登录(旧版登录并注册)' => '~/mapi/index.php?ctl=login&act=do_login&mobile=13950076987&verify_coder=9999',
                    '手机号+密码登录' => '~/mapi/index.php?ctl=login&act=login_by_loginid_with_password&mobile=13950076987&password=123456',
                    '手机号注册' => '~/mapi/index.php?ctl=login&act=regist_by_mobile_with_password&mobile=13950076987&password=123456&verify_coder=1234',
                    '忘记密码' => '~/mapi/index.php?ctl=user_center&act=update_password&mobile=13950076987&password=123456&verify_coder=1234',
                ),
            ),
        ),
    ),
    /* 服务器-localhost */
    'localhost' => array(
        'host' => ['127.0.0.1:82'], // 服务器,支持多个替换
        /* 项目 */
        'project' => array(
            'test_admin_login' => array(
                'title' => '测试管理员注册和登录',
                'items' => array(
                    '管理员后台' => '~/index.php/liveadmin/login/index.html',
                    '用户名密码注册' => '~/index.php/liveapi/admin.registration/regist_by_loginid_with_password/loginid/admin/password/123456/password_confirm/123456/captcha_code/1234.html',
                    '用户名密码登录' => '~/index.php/liveapi/admin.login/login_by_loginid_with_password/loginid/admin/password/123456.html',
                    '发送短信验证码' => '~/index.php/liveapi/sms/send/mobile/13950076987/usefor/registration.html',
                    '手机验证码注册' => '~/index.php/liveapi/admin.registration/regist_by_mobile_with_smscode/mobile/13950076987/password/123/password_confirm/123/smscode/123.html',
                    '手机密码登录' => '~/index.php/liveapi/admin.login/login_by_mobile_with_password/mobile/13950076987/password/123456.html',
                    '手机验证码登录' => '~/index.php/liveapi/admin.login/login_by_mobile_with_sms/mobile/13950076987/sms_code/1234.html',
                    '邮件找回密码' => '~',
                    '短信找回密码' => '~',
                ),
            ),
            'test_login' => array(
                'title' => '测试用户注册和登录',
                'items' => array(
                    '用户名密码注册' => '~/index.php/liveapi/registration/regist_by_loginid_with_password/loginid/sutroon/password/123456/password_confirm/123/captcha_code/1234.html',
                    '用户名密码登录' => '~/index.php/liveapi/login/login_by_loginid_with_password/loginid/sutroon/password/123456.html',
                    '登录会话信息鉴权验证' => '~/index.php/liveapi/index/index/access_token/123456.html',
                    '发送短信验证码' => '~/index.php/liveapi/sms/send/mobile/13950076987/usefor/registration.html',
                    '手机验证码注册' => '~/index.php/liveapi/registration/regist_by_mobile_with_smscode/mobile/13950076987/password/123/password_confirm/123/smscode/123.html',
                    '手机密码登录' => '~/index.php/liveapi/login/login_by_mobile_with_password/mobile/13950076987/password/123456.html',
                    '手机验证码登录' => '~/index.php/liveapi/login/login_by_mobile_with_sms/mobile/13950076987/sms_code/1234.html',
                    '邮件找回密码' => '~',
                    '短信找回密码' => '~',
                ),
            ),
            'test_wxlogin' => array(
                'title' => '测试第三方注册和登录',
                'items' => array(
                    '微信公众号注册并登录' => '~',
                    '微信H5网站扫码注册登录' => '~',
                    '微信APP注册并登录' => '~',
                    'QQ注册并登录' => '~',
                    '新浪注册并登录' => '~',
                ),
            ),
            'test_payment' => array(
                'title' => '测试支付',
                'items' => array(
                ),
            ),
            'test_live' => array(
                'title' => '测试直播',
                'items' => array(
                    '获取腾讯互动直播推流地址' => '~/index.php/liveapi/tencent.live_api/get_live_push_stream_url.html',
                    '获取腾讯互动直播拉流地址' => '~/index.php/liveapi/tencent.live_api/get_live_pull_stream_url.html',
                ),
            ),
            'test_vod' => array(
                'title' => '测试回播',
                'items' => array(
                ),
            ),
        ),
    ),
    'current_server' => 'localhost', // 指定当前活动的服务器
);

// 浏览器参数
$httpHost = $_SERVER['HTTP_HOST']; // 当前服务器域名或ip
$curProjectId = input('project');
$curServId = input('server', $testingConfig['current_server']);
if (!$curServId || !array_key_exists($curServId, $testingConfig)) {
    exit('服务器ID [' . $curServId . '] 未配置');
}
$curTestConf = $testingConfig[$curServId]; // 当前测试配置数据
// 导出查询数据到Markdown文件
if (input('action') == 'exportmd') {
    header("Content-type:application/octet-stream");
    header("Content-Disposition:attachment; filename=itestlist-" . date('Ymd-His') . ".txt");
    if ($curProjectId && array_key_exists($curProjectId, $curTestConf['project'])) {
        $projects[] = $curTestConf['project'][$curProjectId];
    } else {
        $projects = $curTestConf['project'];
    }
    if ($projects) {
        $items = array();
        foreach ($projects as $project) {
            echo '### ', $project['title'], PHP_EOL, PHP_EOL;
            $items = $project['items'];
            foreach ($items as $text => $url) {
                echo $text, ' ', $url, PHP_EOL, PHP_EOL;
            }
        }
    } else {
        echo '没有查询到结果!';
    }
    echo PHP_EOL;
    exit();
}
?>
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title><?php echo APPNAME, ' - ', VERSION ?></title>
        <style type="text/css">
            body, td, th{font-size:13px; color:#333;}
            h1 a { color:#555; font-weight: normal;}
            fieldset { width:97%; margin-bottom: 10px;}
            iframe{width:100%;}
            a{ text-decoration: none;}
            .tb_url {width:60%;}
        </style>
    </head>
    <body>
        <script src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
        <?php
        echo '<h1>', APPNAME, ' ';
        echo '<a href="?token=', $token, '" title="新开一个页面" target="_blank" style="font-size:12px;">[新窗口]</a>';
        echo '<a href="?token=', $token, '" title="重置当前页面" style="font-size:12px;">[重置]</a>';
        echo '<a href="#" title="刷新当前页面" style="font-size:12px;" onclick="location.reload()">[刷新]</a>';
        echo '</h1>';
        // 用户登录信息
        if (isset($loginid)) {
            echo $loginid;
            echo ' [<a href="?action=logout" onclick="return confirm(\'您确定要注销登录吗?\');">注销登录</a>]';
        }
        echo ' 客户端IP:', $_SERVER['REMOTE_ADDR'], ' <span style="display:none">服务器IP:' . $httpHost . '</span> <a href="#" id="lnk-toggle-ipinfo">[...]</a>';
        echo ' <span>便捷工具：<a href="myadminlite.php" target="_blank">[myadminlite]</a><a href="redisadminlite.php" target="_blank">[redisadminlite]</a></span>';
        echo '<div style="margin:5px 0;">测试项目：<select id="slt-proj"><option value="">==选择项目==</option>';
        $curProjects = isset($curTestConf['project']) ? $curTestConf['project'] : array(); // 当前服务器所有项目
        $curItems = array(); // 测试项目明细
        $curReplaceVarsStr = ''; // 可替换的url变量
        foreach ($curProjects as $projId => $projData) {
            echo '<option value="', $projId, '"', ($curProjectId == $projId ? ' selected="selected"' : ''), ' data-url="?server=', $curServId, '&project=', urlencode($projId), '&win_count=', input('win_count'), '&col_count=', input('col_count'), '">', (empty($projData['title']) ? $projId : $projData['title']), '</option>';
            if ($curProjectId == $projId) {
                $curItems = $projData['items'];
                if (isset($projData['replace_vars'])) {
                    $replaceVarsArr = $projData['replace_vars'];
                    foreach ($replaceVarsArr as $paraName => $paraVal) {
                        if ($curReplaceVarsStr) {
                            $curReplaceVarsStr .= "\n";
                        }
                        $curReplaceVarsStr .= "$paraName:$paraVal";
                    }
                }
            }
        }
        $winCountDef = empty($curItems) ? 0 : count($curItems);
        echo '</select>(', $winCountDef, '个接口) <a href="?server=', $curServId, '&project=', $curProjectId, '&action=exportmd" target="_blank" title="导出到MarkDown文件">[导出]</a></div>';
        $winCount = input('win_count');
        if (!$winCount) {
            $winCount = $winCountDef > 10 ? $winCountDef : 10;
        }
        $colCount = input('col_count', 1);
        if (!$colCount) {
            $colCount = 1;
        }
        // 窗口数量
        echo '<div style="float:left;">';
        echo '<fieldset style="width:240px;"><legend>基础设置<a href="#" class="lnk-switch">[-]</a></legend><div class="fieldset_content">';
        echo '<form>窗口数量：<input type="number" name="win_count" value="', $winCount, '" size="2" /><br />分栏数量：<input type="number" name="col_count" value="', $colCount, '" size="2" /><button type="submit">设置</button><input type="hidden" name="server" value="', $curServId, '" /><input type="hidden" name="project" value="', $curProjectId, '" /></form>';
        echo '</div></fieldset>';
        echo '<fieldset style="width:240px;"><legend>主机替换<a href="#" class="lnk-switch">[-]</a></legend><div class="fieldset_content">';
        if (!empty($curTestConf['host'])) {
            $httpHost = current($curTestConf['host']);
        }
        echo '<input type="text" id="txt-host" value="', $httpHost, '" size="25" />';
        // 主机切换
        if (!empty($curTestConf['host'])) {
            echo '<div style="margin:3px 0"><select id="slt-host"><option value="">==切换主机==</option>';
            foreach ($curTestConf['host'] as $host) {
                echo '<option value="', $host, '">', $host, '</option>';
            }
            echo '</select></div>';
        }
        echo '<button id="btn-replace-host">替换</button></div></fieldset>';
        echo '</div>';
        // 变量替换
        $placeholder = "每个变量一行,例如:\nvar1:val1\nvar2:val2";
        echo '<div style="float:left;"><fieldset style="width:710px;"><legend>URL变量批量替换<a href="#" class="lnk-switch">[-]</a></legend><div class="fieldset_content"><textarea id="tb-replace-vars" cols="90" rows="', (empty($curTestConf['host']) ? 7 : 10), '" placeholder="', $placeholder, '">', $curReplaceVarsStr, '</textarea><br /><button type="button" id="btn-replace">替换</button></div></fieldset></div>';
        echo '<div style="clear:left"></div>';
        $adv_urls = array(
            'aTool在线HTTP接口测试' => 'http://www.atool.org/httptest.php',
            'Getman在线HTTP接口测试' => 'https://getman.cn/',
            'ouapi在线HTTP接口测试' => 'http://www.ouapi.com/',
        );
        if ($adv_urls) {
            echo '<fieldset><legend>更多在线HTTP接口测试工具<a href="#" class="lnk-switch">[-]</a></legend><div class="fieldset_content">';
            foreach ($adv_urls as $title => $url) {
                echo '<a href="', $url, '" target="_blank">[', $title, ']</a> ';
            }
            echo '</div></fieldset>';
        }
        echo '<div style="color:#999;margin-bottom:10px;font-size:11px;">友情提示：<span style="color:#F00;">打开浏览器的调试窗口,可以达到事半功倍的效果哦!</span></div>';
        $i = 1;
        $colStyle = $colCount > 1 ? ' style="float:left; width:' . floor(100 / $colCount) . '%"' : '';
        if ($winCountDef > 0) {
            foreach ($curItems as $title => $url) {
                if ($url) {
                    if ($url[0] == '~') {
                        $url = 'http://' . $httpHost . substr($url, 1);
                    }
                } else {
                    $url = 'http://' . $httpHost;
                }
                buildWindow($i, $colStyle, $title, $url);
                $i++;
            }
        }
        while ($i <= $winCount) {
            buildWindow($i, $colStyle, '', 'http://' . $httpHost);
            $i++;
        }
        echo '<div style="clear:left"></div>';
        ?>

        <script type="text/javascript">
            // 2017-11-7 主机快捷切换
            $('#slt-host').change(function () {
                var val = $(this).val();
                if (val) {
                    $('#txt-host').val(val);
                }
            })
            // 2017-10-28 服务器信息改为显隐控制，提高安全性，防止被窥探
            $('#lnk-toggle-ipinfo').click(function () {
                var $a = $(this);
                if ($a.text() == '[...]') {
                    $a.text('[x]').prev().show();
                } else {
                    $a.text('[...]').prev().hide();
                }
                return false;
            })
            $('#slt-proj').change(function () {
                var $slt = $(this);
                if (!$slt.val()) {
                    return;
                }
                var url = $slt.find('option[value="' + $slt.val() + '"]').data('url');
                location.href = url;
            })
            $('input[name="count"]').blur(function () {
                if ($(this).val() == '') {
                    $(this).val(10);
                }
            })
            // 展开或收起fieldset内容
            $('legend .lnk-switch').click(function () {
                var 　 $a = $(this);
                if ($a.text() == '[-]') {
                    $a.text('[+]').attr('title', '展开');
                    $a.parent().parent().find('.fieldset_content').hide();
                } else {
                    $a.text('[-]').attr('title', '收起');
                    $a.parent().parent().find('.fieldset_content').show();
                }
            })
            $('.btn_open').click(function () {
                var $fieldset = $(this).parent();
                var url = $fieldset.find('.tb_url').val();
                var $ifrm = $fieldset.find('iframe');
                if (url.length < 1) {
                    return;
                }
                var isIframeType = true;
                if (isIframeType) {
                    $ifrm.attr('src', url);
                    return;
                }
                // 2017-11-7 see: https://stackoverflow.com/questions/17694807/how-to-set-custom-http-headers-when-changing-iframe-src
                $.ajax({
                    type: "GET",
                    url: url,
                    contentType: "application/json",
                    beforeSend: function (xhr, settings) {
                        xhr.setRequestHeader("some_custom_header", "foo");
                    },
                    success: function (data) {
                        $ifrm.attr('src', "about:_blank")
                        $ifrm.contents().find('html').html(data);
                    }
                });
            })
            $('.btn_open_in_new_window').click(function () {
                var $fieldset = $(this).parent();
                var $input = $fieldset.find('.tb_url');
                if ($input.val().length < 1) {
                    return;
                }
                window.open($input.val());
            })
            /**
             * 替换url中的变量
             * @param string url
             * @param string arg
             * @param string val
             * @param boolean append 如果变量不存在是否追加
             * @returns {@var;url|String|@var;replaceText}
             * @since 2017-10-23
             * @see http://www.cnblogs.com/doseoer/p/5747715.html
             */
            function replace_url_param(url, arg, val, append) {
                var pattern = arg + '=([^&]*)';
                var replaceText = arg + '=' + val;
                if (url.match(pattern)) {
                    url = url.replace(eval('/(' + arg + '=)([^&]*)/gi'), replaceText)
                } else {
                    if (append) {
                        url = url + (url.match('[\?]') ? '&' : '?') + replaceText;
                    }
                }
                return url;
                //return url.match(pattern) ? url.replace(eval('/(' + arg + '=)([^&]*)/gi'), replaceText) : (url.match('[\?]') ? url + '&' + replaceText : url + '?                    ' + replaceText);
            }
// 批量替换url主机信息 2017-10-25
            $('#btn-replace-host').click(function () {
                var txt = $('#txt-host').val();
                if (!txt) {
                    return;
                }
                var $location, url;
                $('.tb_url').each(function () {
                    $location = $(this);
                    url = $location.val();
                    if (url) {
                        url = url.replace(/(http[s]?:\/\/)([^\/]*)/, "$1" + txt);
                        $location.val(url);
                    }
                });
            })
// 批量替换url变量
            $('#btn-replace').click(function () {
                var txt = $('#tb-replace-vars').val();
                if (txt.indexOf(':') < 0) {
                    return;
                }
                var $location, url;
                var txtarr = txt.split("\n");
                var txtitemarr;
                $('.tb_url').each(function () {
                    $location = $(this);
                    url = $location.val();
                    if (url) {
                        for (var i in txtarr) {
                            txtitemarr = txtarr[i].split(':', 2);
                            url = replace_url_param(url, txtitemarr[0], txtitemarr[1]);
                        }
                        $location.val(url);
                    }
                });
            })
// 定时刷新 2017-10-27
            $('.timer input').blur(function () {
                var $input = $(this);
                var time = $input.val();
                if (!time || (time && time < 0)) {
                    $input.val(0);
                    return;
                }
                var interval = $input.data('interval');
                if (interval) {
                    clearInterval(interval);
                }
                if (time == 0) {
                    var $span = $input.parent().find('span');
                    $span.text('                    0/');
                    $span.data('n', 0);
                    return;
                }
                interval = setInterval(countDown_(this), 1000);
                $input.data('interval', interval);
            })
// 解决setInterval无法传参
            function countDown_(input) {
                return function () {
                    countDown(input);
                }
            }
            function countDown(input) {
                var $input = $(input);
                var second = $input.val();
                if (second < 1) { // 如果中途输入0会自动停止
                    var interval = $input.data('interval');
                    if (interval) {
                        clearInterval(interval);
                    }
                    return;
                }
                var $span = $input.parent().find('span');
                var n = $span.data('n');
                if (!n) {
                    n = 0;
                }
                n++;
                if (n > second) {
                    var $fieldset = $span.parent().parent();
                    $fieldset.find('iframe').attr('src', "_blank");
                    $fieldset.find('iframe').attr('src', $fieldset.find('.tb_url').val());
                    n = 1;
                }
                $span.data('n', n);
                $span.text(n + '/')
            }
        </script>
    </body>
</html>
<?php

function input($name, $defv = '') {
    return isset($_REQUEST[$name]) ? $_REQUEST[$name] : $defv;
}

/**
 * 生成子窗体
 * @param type $i 编号
 * @param type $colStyle 分栏样式
 * @param type $title 标题
 * @param type $url 网址
 * @version 2017-10-27
 */
function buildWindow($i, $colStyle, $title, $url) {
    echo '<div', $colStyle, '><fieldset><legend>', $i, '.<a href="#" class="lnk-switch" title="展开或收起当前区块的内容">[-]</a><input placeholder="名称" value="', $title, '" size="60" /></legend><div style="display:none">请求方式：<input type="radio" name="result_type', $i, '" value="ajax" />AJAX<input type="radio" name="result_type', $i, '" value="iframe" checked="checked" />IFrame</div><input type="text" class="tb_url" placeholder="http://" value="', $url, '" /><button class="btn_open">打开</button><button class="btn_open_in_new_window">新窗口打开</button> <span class="timer">定时刷新<span>0/</span><input type="number" step="5" value="0" style="width:60px;" title="0表示停止计时器,输入正整数并点击页面任意空白处即可启动定时器" /><a href="###"title="定时器使用说明' . PHP_EOL . '1. 鼠标移开输入框,并点击任意页面空白处即可启动定时器.' . PHP_EOL . '2. 建议设置值不少于10秒,防止整个页面卡死.' . PHP_EOL . '3. 设置值为0,则自动停止定时器.">(?)</a></span>';
    echo '<div class="fieldset_content"><iframe src="about:_blank" height="230"></iframe></div>';
    echo '</fieldset></div>';
}

/**
 * 用户登录认证类
 * @version 2017-12-15
 */
class JWTLite {

    /**
     * TOKEN_EXP 超时时间 (1 hours, 1 minutes, 30 seconds)
     * @var type 
     */
    public static $TOKEN_EXP = '1 hours';

    /**
     * TOKEN_AUD 允许运行的服务器列表(适用于分布式环境,如果是负载均衡+高防IP环境,则填入高防IP即可),多个之间以逗号隔开,中间不要有空格
     * @var type 
     */
    public static $TOKEN_AUD = '127.0.0.1,47.95.44.177,47.94.100.12,47.93.45.33,47.93.42.85';

    /**
     * TOKEN签名密钥
     * @var type 
     */
    public static $TOKEN_KEY = 'InZ7!';

    /**
     * TOKEN自动延期时长, 每次页面跳转操作会重新生成一个延期过的TOKEN,类似session的效果,只要一直操作就一直不会过期
     * @var type 
     */
    public static $TOKEN_REFRESH_SECOND = 1800;

    /**
     * 页面访问验证配置
     * @var type 
     */
    public static $AUTH_CONFIG = array();

    /**
     * 登录结果
     * @var type compact('loginid', 'token', 'loginexp')
     */
    public static $LOGIN_RESULT = array();

    /**
     * 生成签名
     * @param array $data
     * @return array array(exp,sign,...)
     * @version 2017-11-3
     */
    public static function gen_sign(array $data = array()) {
        $tokenInfo = array(
            "exp" => strtotime("30 minutes"), // 默认TOKEN有效期30分钟
            "aud" => self::$TOKEN_AUD,
        );
        $data = array_merge($tokenInfo, $data);
        ksort($data);
        $md5Str = md5(self::$TOKEN_KEY . json_encode($data));
        $data['sign'] = substr($md5Str, 20, 6);
        unset($data['aud']);
        return $data;
    }

    /**
     * 验证签名
     * @param array $data array(exp,sign,...)
     * @return array
     * @version 2017-11-3
     */
    public static function check_sign($data) {
        if (empty($data['exp']) || empty($data['sign'])) {
            return array('status' => false, 'info' => '签名内容不规范');
        }
        $exp = $data['exp'];
        if ($exp < time()) {
            return array('status' => false, 'info' => '签名已过期');
        }
        if (self::$TOKEN_AUD) {
            $audArr = explode(',', self::$TOKEN_AUD);
            $hostIp = self::get_host_ip();
            if (!in_array($hostIp, $audArr)) {
                return array('status' => false, 'info' => '服务器不在白名单<span style="display:none">' . $hostIp . '</span>');
            }
        }
        $postSign = $data['sign'];
        unset($data['sign']);
        $data['aud'] = self::$TOKEN_AUD;
        ksort($data); // aud,exp,user
        $localSign = substr(md5(self::$TOKEN_KEY . json_encode($data)), 20, 6);
        if ($localSign != $postSign) {
            return array('status' => false, 'info' => '签名非法');
        }
        return array('status' => true, 'info' => '');
    }

    /**
     * 获取表单令牌
     * @return string
     * @version 2017-11-3 由session改为token
     */
    public static function get_token($data) {
        return base64_encode(json_encode(self::gen_sign($data)));
    }

    /**
     * 解析token
     * @param string $post_token
     * @return array
     * @version 2017-11-3 added.
     */
    public static function parse_token($post_token = '') {
        if (!$post_token) {
            $post_token = htmlspecialchars(trim(input('token')));
        }
        if (!$post_token) {
            return ['status' => false, 'info' => 'TOKEN为空'];
        }
        $json = base64_decode($post_token);
        if (false === $json) {
            return ['status' => false, 'info' => 'TOKEN内容无效'];
        }
        $post = json_decode($json, true);
        if (!$post) {
            return ['status' => false, 'info' => 'TOKEN格式错误'];
        }
        $sigResult = self::check_sign($post);
        if (!$sigResult['status']) {
            return $sigResult;
        }
        return ['status' => true, 'info' => $post];
    }

    /**
     * 生成新的延期过的token
     * @param integer $exp 延期时间
     * @param string $token 旧的token字符串
     * @return string
     * @version 2017-11-29
     */
    public static function refresh_token($exp, $token) {
        $tokenInfo = self::parse_token($token);
        $data = $tokenInfo['info'];
        unset($data['sign']);
        $data['exp'] = strtotime(self::$TOKEN_EXP) + self::$TOKEN_REFRESH_SECOND;
        return self::get_token($data);
    }

    /**
     * 用户登录操作
     * @param type $admins
     * @param type $urlparams 额外的浏览器参数
     */
    public static function dologin($admins, $urlparams = array()) {
        $posttoken = htmlspecialchars(input('login_token'));
        if ($posttoken) {
            $tokenInfo = self::parse_token($posttoken);
            $goback_btn = '<a href="?login">[返回]</a>';
            if (!$tokenInfo['status']) {
                exit($tokenInfo['info'] . $goback_btn);
            }
            $loginid = strval(htmlspecialchars(input('user')));
            $loginpwd = htmlspecialchars(input('pass'));
            if (!$loginid || !$loginpwd) {
                exit('表单填写不完整!' . $goback_btn);
            }
            if (!array_key_exists($loginid, $admins)) {
                exit('帐号不存在!' . $goback_btn);
            }
            if ($loginpwd != $admins[$loginid]) {
                exit('密码错误!' . $goback_btn);
            }
            $url = '?token=' . self::get_token(array('exp' => strtotime(self::$TOKEN_EXP), 'user' => $loginid));
            if ($urlparams) {
                $url .= '&' . http_build_query($urlparams);
            }
            header('location:' . $url);
        } else {
            echo '<form method="post" action="?action=login">';
            echo '<input type="text" name="user" placeholder="用户名" required="required" />';
            echo '<input type="password" name="pass" placeholder="密码" required="required" />';
            echo '<input type="hidden" name="login_token" value="', self::get_token(array('exp' => strtotime('3 minutes'), 'action' => 'login')), '" />';
            if ($urlparams) {
                foreach ($urlparams as $key => $val) {
                    echo '<input type="hidden" name="', $key, '" value="', $val, '" />';
                }
            }
            echo '<button type="submit">登录</button><button type="reset">重置</button> ';
            echo $_SERVER['REMOTE_ADDR'];
            echo '</form>';
        }
    }

    /**
     * 访问权限验证
     * @param array $urlparams
     */
    public static function check_login($urlparams = array()) {
        $curAuthtypeId = self::$AUTH_CONFIG['current_authtype'];
        $curAuthStatus = true; // 权限验证状态
        if ($curAuthtypeId == 2 || $curAuthtypeId == 3) { // IP验证
            $http_host = $_SERVER['REMOTE_ADDR'];
            if (!in_array($http_host, self::$AUTH_CONFIG['ips'])) {
                $curAuthStatus = false; // IP验证失败
                if ($curAuthtypeId == 2) {
                    exit('IP [' . $http_host . '] 禁止访问!');
                }
            }
        }

        $loginid = false; // 用户登录ID
        $loginexp = ''; // token过期时间提示
        $token = htmlspecialchars(trim(input('token'))); // 用户登录会话Token
        if ($curAuthtypeId == 1 || ($curAuthtypeId == 3 && !$curAuthStatus)) { // 帐号密码验证
            if ($token) { // 解析登录会话token信息
                $tokenInfo = self::parse_token($token);
                if ($tokenInfo['status']) {
                    $loginid = $tokenInfo['info']['user'];
                    $loginexp = date('Y-m-d H:i:s', $tokenInfo['info']['exp']);
                    // 自动续期功能 2017-11-29
                    if (self::$TOKEN_REFRESH_SECOND > 0) {
                        $refreshToken = self::refresh_token(self::$TOKEN_REFRESH_SECOND, $token);
                        $token = $refreshToken;
                    }
                } else {
                    echo '<div style="font-size:12px; color:#F00">', $tokenInfo['info'], '</div>';
                }
            }
            // 注销登录
            if (input('action') == 'logout') {
                header('location:?logout-success');
                exit;
            }
            if (!$loginid) {
                self::dologin(self::$AUTH_CONFIG['admins'], $urlparams);
                exit;
            }
        }
        self::$LOGIN_RESULT = compact('loginid', 'token', 'loginexp');
    }

    /**
     * 返回服务器IP
     * 参考：SERVER_NAME 和HTTP_HOST的区别 (http://blog.sina.com.cn/s/blog_6d96d3160100q39x.html)
     * @since 1.0 2017-10-13 SoChishun Added.
     */
    public static function get_host_ip() {
        //return $_SERVER['HTTP_HOST']; // 有服务器域名则优先返回域名,否则返回服务器ip
        return gethostbyname($_SERVER['SERVER_NAME']); // 返回服务器IP
    }

}
